luci-app-adblock: sync with update 4.4.2-1
authorDirk Brenken <[email protected]>
Fri, 30 May 2025 16:42:18 +0000 (18:42 +0200)
committerDirk Brenken <[email protected]>
Fri, 30 May 2025 16:42:37 +0000 (18:42 +0200)
Various fixes, a new modal reporting GeoIP map of blocked domains.

Signed-off-by: Dirk Brenken <[email protected]>
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/dnsreport.js
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/feeds.js
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/map.html [new file with mode: 0644]
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js
applications/luci-app-adblock/root/usr/share/luci/menu.d/luci-app-adblock.json

index e8806db3e9ee8f55a06811887a31081e81d02532..07f1ec0da4611f7704a24471d31c20f37ab43ac2 100644 (file)
@@ -2,6 +2,7 @@
 'require view';
 'require fs';
 'require ui';
+'require uci';
 
 /*
        button handling
@@ -200,19 +201,65 @@ function handleAction(ev) {
                ]);
                document.getElementById('refresh').focus();
        }
+
+       if (ev === 'map') {
+               let md = L.ui.showModal(null, [
+                       E('div', { id: 'mapModal',
+                                               style: 'position: relative;' }, [
+                               E('iframe', {
+                                       id: 'mapFrame',
+                                       src: L.resource('view/adblock/map.html'),
+                                       style: 'width: 100%; height: 80vh; border: none;'
+                               }),
+                       ]),
+                       E('div', { 'class': 'right' }, [
+                               E('button', {
+                                       'class': 'btn cbi-button',
+                                       'click': ui.createHandlerFn(this, function (ev) {
+                                               ui.hideModal();
+                                               sessionStorage.clear();
+                                               location.reload();
+                                       })
+                               }, _('Cancel')),
+                               ' ',
+                               E('button', {
+                                       'class': 'btn cbi-button-action',
+                                       'click': ui.createHandlerFn(this, function (ev) {
+                                               const iframe = document.getElementById('mapFrame');
+                                               iframe.contentWindow.location.reload();
+                                       })
+                               }, _('Map Reset'))
+                       ])
+               ]);
+               md.style.maxWidth = '90%';
+               document.getElementById('mapModal').focus();
+       }
 }
 
 return view.extend({
        load: function() {
-               return L.resolveDefault(fs.exec_direct('/etc/init.d/adblock', ['report', 'json', '10', '50', '+']),'');
+               return Promise.all([
+                       L.resolveDefault(fs.exec_direct('/etc/init.d/adblock', ['report', 'json', '10', '50', '+']),''),
+                       uci.load('adblock')
+               ]);
        },
 
        render: function(dnsreport) {
-               if (!dnsreport) {
-                       dnsreport = '{}';
-               };
-               var content;
-               content = JSON.parse(dnsreport);
+               let content, notMsg, errMsg;
+
+               if (dnsreport) {
+                       try {
+                               content = JSON.parse(dnsreport[0]);
+                       } catch (e) {
+                               content = "";
+                               if (!errMsg) {
+                                       errMsg = true;
+                                       ui.addNotification(null, E('p', _('Unable to parse the report file!')), 'error');
+                               }
+                       }
+               } else {
+                       content = "";
+               }
 
                var rows_top = [];
                var tbl_top  = E('table', { 'class': 'table', 'id': 'top_10' }, [
@@ -227,28 +274,28 @@ return view.extend({
                ]);
 
                var max = 0;
-               if (content.top_clients && content.top_domains && content.top_blocked) {
-                       max = Math.max(content.top_clients.length, content.top_domains.length, content.top_blocked.length);
+               if (content[0].top_clients && content[0].top_domains && content[0].top_blocked) {
+                       max = Math.max(content[0].top_clients.length, content[0].top_domains.length, content[0].top_blocked.length);
                }
                for (var i = 0; i < max; i++) {
                        var a_cnt = '\xa0', a_addr = '\xa0', b_cnt = '\xa0', b_addr = '\xa0', c_cnt = '\xa0', c_addr = '\xa0';
-                       if (content.top_clients[i]) {
-                               a_cnt = content.top_clients[i].count;
+                       if (content[0].top_clients[i]) {
+                               a_cnt = content[0].top_clients[i].count;
                        }
-                       if (content.top_clients[i]) {
-                               a_addr = content.top_clients[i].address;
+                       if (content[0].top_clients[i]) {
+                               a_addr = content[0].top_clients[i].address;
                        }
-                       if (content.top_domains[i]) {
-                               b_cnt = content.top_domains[i].count;
+                       if (content[0].top_domains[i]) {
+                               b_cnt = content[0].top_domains[i].count;
                        }
-                       if (content.top_domains[i]) {
-                               b_addr = '<a href="https://duckduckgo.com/?q=' + encodeURIComponent(content.top_domains[i].address) + '&amp;k1=-1&amp;km=l&amp;kh=1" target="_blank" rel="noreferrer noopener" title="Domain Lookup">' + content.top_domains[i].address + '</a>';
+                       if (content[0].top_domains[i]) {
+                               b_addr = '<a href="https://ip-api.com/#' + encodeURIComponent(content[0].top_domains[i].address) + '" target="_blank" rel="noreferrer noopener" title="Domain Lookup">' + content[0].top_domains[i].address + '</a>';
                        }
-                       if (content.top_blocked[i]) {
-                               c_cnt = content.top_blocked[i].count;
+                       if (content[0].top_blocked[i]) {
+                               c_cnt = content[0].top_blocked[i].count;
                        }
-                       if (content.top_blocked[i]) {
-                               c_addr = '<a href="https://duckduckgo.com/?q=' + encodeURIComponent(content.top_blocked[i].address) + '&amp;k1=-1&amp;km=l&amp;kh=1" target="_blank" rel="noreferrer noopener" title="Domain Lookup">' + content.top_blocked[i].address + '</a>';
+                       if (content[0].top_blocked[i]) {
+                               c_addr = '<a href="https://ip-api.com/#' + encodeURIComponent(content[0].top_blocked[i].address) + '" target="_blank" rel="noreferrer noopener" title="Domain Lookup">' + content[0].top_blocked[i].address + '</a>';
                        }
                        rows_top.push([
                                a_cnt,
@@ -274,16 +321,16 @@ return view.extend({
                ]);
 
                max = 0;
-               if (content.requests) {
+               if (content[0].requests) {
                        var button;
-                       max = content.requests.length;
+                       max = content[0].requests.length;
                        for (var i = 0; i < max; i++) {
-                               if (content.requests[i].rc === 'NX') {
+                               if (content[0].requests[i].rc === 'NX') {
                                        button = E('button', {
                                                'class': 'btn cbi-button cbi-button-positive',
                                                'style': 'word-break: inherit',
                                                'name': 'allowlist',
-                                               'value': content.requests[i].domain,
+                                               'value': content[0].requests[i].domain,
                                                'click': handleAction
                                        }, [ _('Allowlist...') ]);
                                } else {
@@ -291,16 +338,16 @@ return view.extend({
                                                'class': 'btn cbi-button cbi-button-negative',
                                                'style': 'word-break: inherit',
                                                'name': 'blocklist',
-                                               'value': content.requests[i].domain,
+                                               'value': content[0].requests[i].domain,
                                                'click': handleAction
                                        }, [ _('Blocklist...') ]);
                                }
                                rows_requests.push([
-                                       content.requests[i].date,
-                                       content.requests[i].time,
-                                       content.requests[i].client,
-                                       '<a href="https://duckduckgo.com/?q=' + encodeURIComponent(content.requests[i].domain) + '&amp;k1=-1&amp;km=l&amp;kh=1" target="_blank" rel="noreferrer noopener" title="Domain Lookup">' + content.requests[i].domain + '</a>',
-                                       content.requests[i].rc,
+                                       content[0].requests[i].date,
+                                       content[0].requests[i].time,
+                                       content[0].requests[i].client,
+                                       '<a href="https://ip-api.com/#' + encodeURIComponent(content[0].requests[i].domain) + '" target="_blank" rel="noreferrer noopener" title="Domain Lookup">' + content[0].requests[i].domain + '</a>',
+                                       content[0].requests[i].rc,
                                        button
                                ]);
                        }
@@ -313,19 +360,19 @@ return view.extend({
                                E('p', '\xa0'),
                                E('div', { 'class': 'cbi-value' }, [
                                        E('div', { 'class': 'cbi-value-title', 'style': 'float:left;width:230px' }, _('Start Timestamp')),
-                                       E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'float:left;color:#37c' }, (content.start_date || '-') + ', ' + (content.start_time || '-'))
+                                       E('div', { 'class': 'cbi-value-title', 'id': 'start', 'style': 'float:left;color:#37c' }, (content[0].start_date || '-') + ', ' + (content[0].start_time || '-'))
                                ]),
                                E('div', { 'class': 'cbi-value' }, [
                                        E('div', { 'class': 'cbi-value-title', 'style': 'float:left;width:230px' }, _('End Timestamp')),
-                                       E('div', { 'class': 'cbi-value-title', 'id': 'end', 'style': 'float:left;color:#37c' }, (content.end_date || '-') + ', ' + (content.end_time || '-'))
+                                       E('div', { 'class': 'cbi-value-title', 'id': 'end', 'style': 'float:left;color:#37c' }, (content[0].end_date || '-') + ', ' + (content[0].end_time || '-'))
                                ]),
                                E('div', { 'class': 'cbi-value' }, [
                                        E('div', { 'class': 'cbi-value-title', 'style': 'float:left;width:230px' }, _('Total DNS Requests')),
-                                       E('div', { 'class': 'cbi-value-title', 'id': 'total', 'style': 'float:left;color:#37c' }, content.total || '-')
+                                       E('div', { 'class': 'cbi-value-title', 'id': 'total', 'style': 'float:left;color:#37c' }, content[0].total || '-')
                                ]),
                                E('div', { 'class': 'cbi-value' }, [
                                        E('div', { 'class': 'cbi-value-title', 'style': 'float:left;width:230px' }, _('Blocked DNS Requests')),
-                                       E('div', { 'class': 'cbi-value-title', 'id': 'blocked', 'style': 'float:left;color:#37c' }, (content.blocked || '-') + ' (' + (content.percent || '-') + ')')
+                                       E('div', { 'class': 'cbi-value-title', 'id': 'blocked', 'style': 'float:left;color:#37c' }, (content[0].blocked || '-') + ' (' + (content[0].percent || '-') + ')')
                                ])
                        ]),
                        E('div', { 'class': 'cbi-section' }, [
@@ -342,6 +389,28 @@ return view.extend({
                                ])
                        ]),
                        E('div', { 'class': 'cbi-page-actions' }, [
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-apply',
+                                       'style': 'float:none;margin-right:.4em;',
+                                       'click': ui.createHandlerFn(this, function () {
+                                               if (uci.get('adblock', 'global', 'adb_map') !== '1') {
+                                                       if (!notMsg) {
+                                                               notMsg = true;
+                                                               return ui.addNotification(null, E('p', _('GeoIP Map is not enabled!')), 'info');
+                                                       }
+                                               }
+                                               if (content[1] && content[1].length > 1) {
+                                                       sessionStorage.setItem('mapData', JSON.stringify(content[1]));
+                                                       return handleAction('map');
+                                               }
+                                               else {
+                                                       if (!notMsg) {
+                                                               notMsg = true;
+                                                               return ui.addNotification(null, E('p', _('No GeoIP Map data!')), 'info');
+                                                       }
+                                               }
+                                       })
+                               }, [_('Map...')]),
                                E('button', {
                                        'class': 'btn cbi-button cbi-button-apply',
                                        'style': 'float:none;margin-right:.4em;',
index acfa8995d25f3f7beedfd43342d5ed58900b8358..6a133c343931fab62cb189a7950f990119455995 100644 (file)
@@ -121,11 +121,18 @@ function handleEdit(ev) {
        const nodeKeys = document.querySelectorAll('[id^="widget.cbid.json"][id$="name"]');
        for (let i = 0; i < nodeKeys.length; i++) {
                let subElements = {};
-               let elements = document.querySelectorAll('[id^="widget.cbid.json.' + nodeKeys[i].id.split('.')[3] + '\."]');
+               const elements = document.querySelectorAll('[id^="widget.cbid.json.' + nodeKeys[i].id.split('.')[3] + '\."], \
+                       [id^="cbid.json.' + nodeKeys[i].id.split('.')[3] + '\.rule"]');
                for (const element of elements) {
-                       let key = element.id.split('.')[4];
-                       let value = element.value || "";
-                       if (value === "") {
+                       let key;
+                       const value = element.value || "";
+                       const parts = element.id.split('.');
+                       if (parts.length === 5) {
+                               key = element.id.split('.')[4];
+                       } else if (parts.length === 4) {
+                               key = element.id.split('.')[3];
+                       }
+                       if (!key || value === "") {
                                continue;
                        }
                        switch (key) {
@@ -207,7 +214,7 @@ return view.extend({
                                return true;
                        }
 
-                       o = s.option(form.ListValue, 'rule', _('Rule'));
+                       o = s.option(form.Value, 'rule', _('Rule'));
                        o.value('/^([[:alnum:]_-]{1,63}\\.)+[[:alpha:]]+([[:space:]]|$)/{print tolower($1)}', _('<DOMAIN>'));
                        o.value('/^127\\.0\\.0\\.1[[:space:]]+([[:alnum:]_-]{1,63}\\.)+[[:alpha:]]+([[:space:]]|$)/{print tolower($2)}', _('127.0.0.1<SPACE><DOMAIN>'));
                        o.value('/^0\\.0\\.0\\.0[[:space:]]+([[:alnum:]_-]{1,63}\\.)+[[:alpha:]]+([[:space:]]|$)/{print tolower($2)}', _('0.0.0.0<SPACE><DOMAIN>'));
diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/map.html b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/map.html
new file mode 100644 (file)
index 0000000..e747a59
--- /dev/null
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+       <title>adblock Map</title>
+       <meta charset="utf-8" />
+       <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
+       <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"
+               integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
+       <style>
+               #map {
+                       height: 97vh;
+                       width: 100%;
+               }
+
+               .mono {
+                       font-size: small;
+                       font-family: monospace;
+                       white-space: nowrap;
+                       margin: 0.3em !important;
+               }
+       </style>
+</head>
+
+<body>
+       <div id="map"></div>
+
+       <script src="https://unpkg.com/[email protected]/dist/leaflet.js"
+               integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
+       <script>
+               'use strict';
+
+               /* get mapData */
+               const mapData = sessionStorage.getItem('mapData');
+               if (mapData) {
+                       let uniqueCoordinates = [];
+                       let homeCoordinates = [];
+                       const parsedData = JSON.parse(mapData);
+                       parsedData.forEach(function (item, index) {
+                               if (Object.keys(item).length !== 0) {
+                                       let keys = Object.keys(item);
+                                       keys.forEach(function (key) {
+                                               if (item[key] && item[key].lat && item[key].lon && item[key].query && item[key].as && item[key].city && item[key].countryCode) {
+                                                       let coordObj = { lat: item[key].lat, lon: item[key].lon };
+                                                       if (key === "homeIP" && !homeCoordinates.some(existingCoord => existingCoord.lat === coordObj.lat && existingCoord.lon === coordObj.lon)) {
+                                                               coordObj = { key: key, lat: item[key].lat, lon: item[key].lon, query: item[key].query, as: item[key].as, city: item[key].city, cc: item[key].countryCode };
+                                                               homeCoordinates.push(coordObj);
+                                                       }
+                                                       if (key !== "homeIP" && !uniqueCoordinates.some(existingCoord => existingCoord.lat === coordObj.lat && existingCoord.lon === coordObj.lon)) {
+                                                               coordObj = { key: key, lat: item[key].lat, lon: item[key].lon, query: item[key].query, as: item[key].as, city: item[key].city, cc: item[key].countryCode };
+                                                               uniqueCoordinates.push(coordObj);
+                                                       }
+                                               }
+                                       });
+                               }
+                       });
+
+                       /* intialize map and map tiles */
+                       let map;
+                       homeCoordinates.forEach(function (coordObj) {
+                               let latHome = coordObj.lat;
+                               let lonHome = coordObj.lon;
+                               let ipHome = coordObj.query.slice(0, 38);
+                               let asHome = coordObj.as.slice(0, 38);
+                               let cityHome = coordObj.city.slice(0, 33);
+                               let ccHome = coordObj.cc;
+
+                               if (typeof map === "undefined") {
+                                       map = L.map('map', {
+                                               zoom: 6,
+                                               minZoom: 2,
+                                               maxZoom: 18,
+                                               center: [latHome, lonHome]
+                                       }).setView([latHome, lonHome]);
+                                       L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png', {
+                                               attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+                                       }).addTo(map);
+                               }
+
+                               /* render markers for local IPs */
+                               let circle = L.circleMarker([latHome, lonHome]).addTo(map);
+                               circle.setStyle({ color: '#378242', opacity: 1.0, fillColor: '#378242', fillOpacity: 0.5, radius: 6 });
+                               circle.bindPopup("<b><center>local IP</center></b>" +
+                                       "<p class=\"mono\">" +
+                                       "City: " + cityHome + " (" + ccHome + ")" +
+                                       "<br />Latitude : " + latHome +
+                                       "<br />Longitude: " + lonHome +
+                                       "<br />IP: " + ipHome +
+                                       "<br />AS: " + asHome +
+                                       "</p>");
+                       });
+
+                       /* render markers for blocked Domains */
+                       uniqueCoordinates.forEach(function (coordObj) {
+                               let key = coordObj.key;
+                               let lat = coordObj.lat;
+                               let lon = coordObj.lon;
+                               let ip = coordObj.query.slice(0, 38);
+                               let as = coordObj.as.slice(0, 38);
+                               let city = coordObj.city.slice(0, 33);
+                               let cc = coordObj.cc;
+                               let circle = L.circleMarker([lat, lon]).addTo(map);
+                               circle.setStyle({ color: '#C22121', opacity: 1.0, fillColor: '#C22121', fillOpacity: 0.5, radius: 2 });
+                               circle.bindPopup("<b><center>" + key + "</center></b>" +
+                                       "<p class=\"mono\">" +
+                                       "City: " + city + " (" + cc + ")" +
+                                       "<br />Latitude : " + lat +
+                                       "<br />Longitude: " + lon +
+                                       "<br />IP: " + ip +
+                                       "<br />AS: " + as +
+                                       "</p>");
+                       });
+               }
+       </script>
+</body>
+
+</html>
\ No newline at end of file
index 6d612fc45243498740bd2cd7d3685286b2714f37..45d26cd1979f3d62d464f11d475a220497f5a9d9 100644 (file)
@@ -110,10 +110,6 @@ return view.extend({
                                if (backend && info) {
                                        backend.textContent = info.dns_backend || '-';
                                }
-                               var utils = document.getElementById('utils');
-                               if (utils && info) {
-                                       utils.textContent = info.run_utils || '-';
-                               }
                                var ifaces = document.getElementById('ifaces');
                                if (ifaces && info) {
                                        ifaces.textContent = info.run_ifaces || '-';
@@ -160,10 +156,6 @@ return view.extend({
                                        E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('DNS Backend')),
                                        E('div', { 'class': 'cbi-value-field', 'id': 'backend', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
                                ]),
-                               E('div', { 'class': 'cbi-value' }, [
-                                       E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Run Utils')),
-                                       E('div', { 'class': 'cbi-value-field', 'id': 'utils', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
-                               ]),
                                E('div', { 'class': 'cbi-value' }, [
                                        E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Run Interfaces')),
                                        E('div', { 'class': 'cbi-value-field', 'id': 'ifaces', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
@@ -282,12 +274,19 @@ return view.extend({
                o = s.taboption('additional', form.Flag, 'adb_debug', _('Verbose Debug Logging'), _('Enable verbose debug logging in case of any processing errors.'));
                o.rmempty = false;
 
-               o = s.taboption('additional', form.Flag, 'adb_nice', _('Low Priority Service'), _('Reduce the priority of the adblock background processing to take fewer resources from the system.'));
-               o.enabled = '10';
+               o = s.taboption('additional', form.ListValue, 'adb_nicelimit', _('Nice Level'), _('The selected priority will be used for adblock background processing.'));
+               o.value('-20', _('Highest Priority'));
+               o.value('-10', _('High Priority'));
+               o.value('0', _('Normal Priority'));
+               o.value('10', _('Less Priority'));
+               o.value('19', _('Least Priority'));
+               o.default = '0';
+               o.placeholder = _('-- default --');
+               o.create = true;
+               o.optional = true;
                o.rmempty = true;
 
-               o = s.taboption('additional', form.Value, 'adb_tmpbase', _('Base Temp Directory'), _('Base temp directory for all adblock related runtime operations, \
-                       e.g. downloading, sorting, merging etc.'));
+               o = s.taboption('additional', form.Value, 'adb_basedir', _('Base Directory'), _('Base working directory during adblock processing.'));
                o.placeholder = '/tmp';
                o.rmempty = true;
 
@@ -410,6 +409,10 @@ return view.extend({
                o = s.taboption('adv_report', form.Flag, 'adb_represolve', _('Resolve IPs'), _('Resolve reporting IP addresses by using reverse DNS (PTR) lookups.'));
                o.rmempty = true;
 
+               o = s.taboption('adv_report', form.Flag, 'adb_map', _('GeoIP Map'), _('Enable a GeoIP map that shows the geographical location of the blocked domains. This requires external requests to get the map tiles and geolocation data.'));
+               o.optional = true;
+               o.rmempty = true;
+
                /*
                        advanced email settings tab
                */
index 807310b957886eb0d7693032861210b2e8475c6b..bc7d2edd866730eb339407ae8d50557b4ccdbda2 100644 (file)
        },
        "admin/services/adblock/logread": {
                "title": "Log View",
-               "order": 50,
+               "order": 60,
                "action": {
                        "type": "view",
                        "path": "adblock/logread"
                }
        }
-}
+}
\ No newline at end of file